基于OpenCPU方案的BC26 NB模组开发总结 |
您所在的位置:网站首页 › 移远 二次开发 › 基于OpenCPU方案的BC26 NB模组开发总结 |
文章目录
一.概述二.模块简介三.编译流程1.开发环境搭建2.工程编译3.程序烧录
四.程序开发流程1.主任务定义2.系统资源初始化3.socket连接流程4.socket连接成功后,发送数据5.在执行初始化工作时,注册了socket接收函数,下面实现它6.dns域名解析
五.总结六. 感谢支持
一.概述
BC26是一款典型的NB模组,支持OpenCPU开发方式,所谓OpenCPU开发方式指的就是模组本身的CPU资源对外部分开放,也就是基于模组本身的固件库提供的API接口做二次开发,以满足不同的业务需要,这样就可以将以往“CPU+模组“的开发方式简化为单”模组“的开发方式。节省了硬件资源,同时避免了CPU和模组之间的通信,提高了通信可靠性和性能。BC26模块就支持Opencpu的开发方案,其提供的API接口是基于FreeRTOS操作系统的。本文就详细介绍BC26模块的OpenCPU开发流程。 二.模块简介BC26 NB模块主要以串口的方式与外部通信,对外主要提供了两个uart串口,一个为主串口main uart,另一个为调试串口debug uart。主串口主要的作用有两个:一个是烧录应用程序和升级版本固件;另一个是AT指令的通信口。当我们使用opencpu方案做开发时,主串口不再作为外部AT指令的通信口而只作为程序的烧录口用。调试串口主要是用来打印一些调试信息。所以说串口是BC26模块对外提供的主要接口。 另外要介绍的就是模块的复位引脚,因为在给模块烧录程序(或固件)的时候需要使模块复位以做烧录同步。 支持的网络协议包括:UDP/ TCP/ LwM2M /MQTT/ SNTP/ CoAP*/ PPP*/ TLS*/ DTLS*/ HTTP*/ HTTPS*。本文主要是基于UDP和TCP协议完成socket编程。 三.编译流程 1.开发环境搭建首先我拿到的是移远官方的SDK开发包(V1.6版本):BC26_QuecOpen_NB1_SDK_V1.6,这里有一个小插曲,我最开始拿到的是V1.5版本,在开发过程中发现V1.5版本不支持DNS协议解析,后来分析发现是SDK版本的问题,升级到V1.6版本后就可以了。拿到这个SDK包之后在windows环境下使用代码编辑工具打开(如:source insight、vscode等都可以),我选用的是vscode编辑器。 2.工程编译SDK开发包中提供make编译工具链,我们只要修改它提供的Makefile文件,将我么自己的添加的工程文件路径放在殡仪目录下就可以了,主要是头文件路径、源文件路径、编译的C文件设置,如下图所示: 在修改好Makefile文件之后,直接打开SDK中的make工具,执行make new重新编译整个工程,执行make编译修改的文件,执行make clean清除工程编译痕迹。make编译工具如下所示: 3.程序烧录在我们使用make new命令编译好整个工程之后会在SDK的build/gcc目录下生成app_image_bin.cfg文件,这就是我们编译完成的目标文件,等会就是将这个文件烧录到BC26模块中,如下所示: 程序的烧录需要使用到SDK提供的QFlash烧录工具,这个工具在tools目录中,打开QFlash后的界面如下: 将BC26的主串口连接到电脑上,在QFlash工具中选中对应的端口号,选择烧录的波特率(大小都可,不过选择较高的波特率烧录速度会加快,这里选择460800)。设置好串口之后点击Load FW Files按钮选中之前我们编译号的目标文件app_image_bin.cfg(切记:这个目标文件存放发路径一定不能包含中文,否则会烧写失败!!!)。选中之后页面会加载出烧录所需的工程文件路径如上图所示。现在一切准备就绪,可以开始烧录了:点击start之后,系统会停留在等待复位的状态,此时我们开机或者复位都可以做烧录同步,烧录结束后会提示PASS并进度条满格为绿色。至此烧录工作完成。 四.程序开发流程BC26是基于FreeRTOS系统做二次开发的,在SDK中提供了多种示例,我是基于tcp和udp示例完成了socket通讯工作,以下结合程序简要阐述开发流程。 1.主任务定义在custom_task_cfg.h文件中创建task,这里我只创建一个主任务proc_main_task: TASK_ITEM(proc_main_task,main_task_id,10*1024,DEFAULT_VALUE1,DEFAULT_VALUE2) //main taskproc_main_task函数原型:调用了net_task任务,也就是我们编写的主代码 void proc_main_task(s32 taskId) { net_task(); //网络任务 } 2.系统资源初始化完成socket网络通信所使用的系统资源主要包括:uart串口,socket网络,flash存储等。SDK已经给我们封装并提供好了各种API,我们只需要调用即可。初始化部分代码如下: //被调主任务 void net_task(void) { ST_MSG msg; s32 ret; u8 read_ip[2]={0}; //读取ip的前两个字节,作为是否已经写入FLASH的标志 u32 bund =0; //波特率 u8 bund_byte[4]; //波特率临时变量 /*1.首先确认是否要对系统配置默认信息*/ Ql_Flash_Read(1,309,read_ip,2); //从FLASH中读取ip地址前两位 if(read_ip[0]==0 && read_ip[1]==0)Write_DefaultConfig_To_FLASH(); //如果FLASH为空,将系统默认配置信息写入FLASH //Write_DefaultConfig_To_FLASH(); /*2.从FLASH中读取波特率并保存到变量bund中*/ Ql_Flash_Read(1,309,bund_byte,4); //从FLASH中读取波特率 bund = bund_byte[0] | bund_byte[1] u8 srvport[10]; u8 *p = NULL; u8 pData[50]={0}; s32 ret; //1.command: Set_Srv_Param=, Ql_sprintf((char *)pData,"Set_Srv_Param=,\r\n",socekt_information->ip_address,socekt_information->port); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"Set_Srv_Param="); if (p) { Ql_memset(m_SrvADDR, 0, SRVADDR_LEN); if (Analyse_Command(pData, 1, '>', m_SrvADDR)) { //APP_DEBUG("SOCKET Address Parameter Error.\r\n"); return QL_NO; //SOCKET地址错误,返回NO } Ql_memset(srvport, 0, 10); if (Analyse_Command(pData, 2, '>', srvport)) { //APP_DEBUG("SOCKET Port Parameter Error.\r\n"); return QL_NO; //SOCKET端口错误,返回NO } socket_param_t.address = Ql_MEM_Alloc(sizeof(u8)*SRVADDR_LEN); if(socket_param_t.address!=NULL) { Ql_memset(socket_param_t.address,0,SRVADDR_LEN); Ql_memcpy(socket_param_t.address,m_SrvADDR,sizeof(m_SrvADDR)); } socket_param_t.remote_port= Ql_atoi(srvport); //APP_DEBUG("Set Server Parameter Successfully,\r\n",socket_param_t.address,socket_param_t.remote_port); } //2.command:SOCKET_ContextID= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"command:SOCKET_ContextID=\r\n",socekt_information->contextid); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_ContextID="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET Contextid Parameter Error.\r\n"); return QL_NO; //SOCKET_ContextID错误,返回NO } socket_param_t.contextID = Ql_atoi(temp_buffer); //APP_DEBUG("Set Contextid Parameter Successfully\r\n",socket_param_t.contextID); } //3.command:SOCKET_ServiceType= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"SOCKET_ServiceType=\r\n",socekt_information->servicetype); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_ServiceType="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET service type Parameter Error.\r\n"); return QL_NO; //SOCKET_Service类型错误,返回NO } socket_param_t.service_type = Ql_atoi(temp_buffer); //APP_DEBUG("Set service type Parameter Successfully\r\n",socket_param_t.service_type); } //4.command:SOCKET_LocalPort= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"command:SOCKET_LocalPort=\r\n",socekt_information->localport); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_LocalPort="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET local port Parameter Error.\r\n"); return QL_NO; //socket本地端口错误,返回NO } socket_param_t.local_port = Ql_atoi(temp_buffer); //APP_DEBUG("Set local port Parameter Successfully\r\n",socket_param_t.local_port); } //5.command:SOCKET_ProtocolType= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"SOCKET_ProtocolType=\r\n",socekt_information->protocoltype); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_ProtocolType="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET protocol type Parameter Error.\r\n"); return QL_NO; //protocol类型错误,返回NO } socket_param_t.protocol_type = Ql_atoi(temp_buffer); //APP_DEBUG("Set protocol type Parameter Successfully\r\n",socket_param_t.protocol_type); } //6.command:SOCKET_Open= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"SOCKET_Open=\r\n",socekt_information->connectid); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_Open="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET Open Error.\r\n"); return QL_NO; //socket打开错误,返回NO } socket_param_t.connectID = Ql_atoi(temp_buffer); ret = RIL_SOC_QIOPEN(&socket_param_t); if(ret == 0) { //APP_DEBUG("\r\n",ret); } else { //APP_DEBUG("\r\n",ret); return QL_NO; //socket打开失败,返回NO } } return QL_OK; //全部配置成功,返回OK }其中socekt_param_user_t结构体表示配置socket的相关参数,具体如下: //定义该结构体表示socekt相关信息 typedef struct socekt_param_user { u8 *ip_address; //IP地址 u8 *port; //端口号 u8 contextid; //context ID, range is 1-3 u8 servicetype; // 选择服务类型 0: Start a TCP connection as a client,1: Start a UDP connection as a client u8 localport; //The local port, range is 1-65535,if is 0, then the local port will be assigned automatically, else the local port is assigned as specified u8 protocoltype; //0: IPv4,1: IPv6 u8 connectid; //socket service index, range is 0-4 }socekt_param_user_t; //结构体类型重定义调用Connect_To_SOCEKT函数建立socket连接部分代码如下: //初始化结构体参数,作为形参传入 socekt_information.ip_address=IP_address; //IP=47.110.249.116 socekt_information.port=portnum; //port=8005 socekt_information.contextid=1; //contextid=1 socekt_information.servicetype=service_type; //servicetype: 1--->UDP 0---->TCP socekt_information.localport=0; //localport=0: 0:自动分配 socekt_information.protocoltype=0; //protocoltype=0 0: IPv4,1: IPv6 socekt_information.connectid=0; //connectid=0 ret=Connect_To_SOCEKT(&socekt_information); //建立SOCEKT连接 Ql_Delay_ms(2000); //延时2S等待连接建立 if(ret==QL_OK) //判断返回是否成功 { APP_DEBUG("socket连接成功,进入透传模式\r\n"); //进入透传模式 } else{ APP_DEBUG("socket连接失败,请重新连接!!!\r\n"); //socket连接失败 } 4.socket连接成功后,发送数据在建立socket连接之后,模组进入透传模式,直接调用相关API函数发送数据,具体函数及调用如下: /** * @brief UDP发送数据 * * @param data_buffer:数据缓存区首地址 * len: 数据长度 * connectid: socket service index, range is 0-4,根据上面Connect_To_UDP()函数确定 * data_type: Data_String表示发送的是String类型数据 * Data_Hex表示发送的是Hex类型数据 * * @return QL_NO 建立连接失败 * QL_OK 建立连接成功 */ u8 SendData_SOCEKT(u8 *data_buffer,u32 len,u8 connectid,u8 data_type) { s32 ret; //1.command: SOCKET_SENDDATA=, 发送String数据 if(data_type==Data_String) { ret = RIL_SOC_QISEND(connectid,len,data_buffer); if (ret == 0) { //APP_DEBUG("\r\n"); }else { //APP_DEBUG("\r\n",ret); return QL_NO; } } //2.command: SOCKET_SENDDATAHEX=, 发送HEX串数据 else if(data_type==Data_Hex) { ret = RIL_SOC_QISENDEX(connectid,len/2,data_buffer); if (ret == 0) { //APP_DEBUG("\r\n"); }else { //APP_DEBUG("\r\n",ret); return QL_NO; } } return QL_OK; //发送成功,返回QL_OK }将串口接收到的数据直接通过模组透传出去: Ql_memset(send_buffer, 0, sizeof(send_buffer)); //清空串口接收缓存区 HexArrayToString(pData,send_buffer,len); //将数据封装到send_buffer里面,数据长度扩大一倍 //APP_DEBUG("send_buffer=%s ,len=%d\r\n",send_buffer,len); SendData_SOCEKT(send_buffer,len*2,socekt_information.connectid,Data_Hex); //发送数据HEX,最大为512字节在透传模式下发送"+++"使模块退出透传模式。 5.在执行初始化工作时,注册了socket接收函数,下面实现它在recv信息中包含相关头信息,这不是透传的信息,因此先使用指针操作去掉头部信息,只留下网络传回的的透传信息并copy到socket_recv_buffer缓存区中。然后直接通过串口传出,至此完成了一个完整的信息透传回路。 void callback_socket_recv(u8* buffer,u32 length) { u8 *p = NULL; u16 recv_len=0; //定义接收数据长度变量 //APP_DEBUG("\r\n",buffer,length); //原始数据 p = Ql_strstr(buffer,"0,"); //获取p指针地址 if(*(p+3)==',') { recv_len=Ql_atoi((p+2)); //获得接收字节数 Ql_memcpy(socket_recv_buffer,p+4,recv_len); } else { recv_len=Ql_atoi((p+2)); Ql_memcpy(socket_recv_buffer,p+5,recv_len); } //APP_DEBUG("%s",socket_recv_buffer); Ql_UART_Write(UART_PORT0, socket_recv_buffer, recv_len ); Ql_memset(socket_recv_buffer, 0, sizeof(socket_recv_buffer)); } 6.dns域名解析有些时候我们需要进行dns域名解析,同样系统给我们提供了API函数,但是dns解析需要花费一定的时间,因此我们开启一个定时器做判断dns是否解析成功。 /** * @brief 域名解析函数: * * @param *hostname:域名 * * @return void */ void get_ip_by_hostname(u8 *hostname) { //APP_DEBUG("hostname=%s\r\n",hostname); //输出服务器域名 Ql_IpHelper_GetIPByHostName(0, hostname, Callback_GetIpByName); //进行域名解析 //开启一个定时器 Ql_Timer_Register(UDP_TIMER_ID, Callback_Timer, NULL); Ql_Timer_Start(UDP_TIMER_ID, UDP_TIMER_PERIOD, TRUE); } static u8 m_ipaddress[IP_ADDR_LEN]; //存储DNS解析完成的IP值 //域名解析回调函数,在此函数中完成dns域名解析工作,并将最终解析出的ip地址存储在全局缓存区m_ipaddress中 void Callback_GetIpByName(u8 contexId,s32 errCode,u32 ipAddrCnt,u8* ipAddr) { if (errCode == SOC_SUCCESS) { Ql_memset(m_ipaddress, 0, IP_ADDR_LEN); Ql_memcpy(m_ipaddress, ipAddr, IP_ADDR_LEN); //APP_DEBUG("\r\n", __func__, contexId,errCode,ipAddrCnt,m_ipaddress); success_flag=1; //DNS解析成功标志置位 } } //定时器中断服务函数,在其中来判断是否dns解析成功并执行相应的动作 static void Callback_Timer(u32 timerId, void* param) { Ql_Timer_Stop(UDP_TIMER_ID); //停止定时器 if(success_flag) { success_flag=0; //DNS解析成功标志复位 APP_DEBUG("CONFIG_CENTER0 SUCCESS,DNS IP ADDRESS IS:%s\r\n",m_ipaddress); } else { APP_DEBUG("dns failed!!!\r\n"); } } 五.总结上述详细介绍了基于OpenCPU方案的BC26开发流程,其中开发的关键在于学会使用SDK提供的开发工具套件以及基于SDK提供的example开发自己的程序。上述并未给出完整的程序,只是针对一些关键点给出操作代码,最终的实现的功能为一个透传模块,根据主串口的命令进入透传模式并发送透传数据,发送+++退出透传模式。 六. 感谢支持完结撒花!希望看到这里的小伙伴能点个关注,我后续会持续更新,也欢迎大家广泛交流。 码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持! |
今日新闻 |
点击排行 |
|
推荐新闻 |
图片新闻 |
|
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭 |